# FIWI, WS 2012
# trend indicator
# core components
# @author Chris Borckholder
from collections import deque

# predefined plot keys
    # signaler plot keys
KEY_LOWER = "lower"         # lower bound of indicator
KEY_UPPER = "upper"         # upper bound of indicator
    # trader plot keys  
KEY_COMMAND = "command"     # signaled command
KEY_ACTION = "action"       # trader took this action
KEY_PROFIT = "profit"       # profit a trader could realize with current position now
KEY_YIELD = "yield"         # profit a trader accumulated if finishing now
KEY_HOLD_AT = "hold_at"     # trader holds a position at this price 

# possible actions
ACTION_NO = "no action"     # did nothing
ACTION_LONG = "long"        # create long position
ACTION_SHORT = "short"      # create short position
ACTION_CLEAR = "clear"      # clear current position

# possible commands
CMD_BUY = "buy"         # long position advised
CMD_SELL = "sell"       # short position advised
CMD_NONE = "none"       # keep position as is

def process(index, actors, histories):
    '''
    Process all day->price pairs provided by the given index. Feed each pair 
    to all given histories. If all histories are ready, feed subsequent pairs 
    also to all given signaler->trader pairs.
    After the index was consumed completely, finish all given traders and return
    the generated lists of MultiValuePoints.
    @type index: Iterable that provides day->price pairs
    @type actors : list of (Name,Signaler,Trader) tuples
    @type histories : List of Histories
    @return: a dictionary holding a list of MultiValuePoints for each given actor
        generated by the signaler->trader pair and a MultValuePoint list with key
        'index' containing the day->price pairs.
    '''
    plot = []
    for day, price in index:
        if all([h.is_ready() for h in histories]):
            now = MultiValuePoint(day, price)
            plot.append(now)
            for name, signaler, trader in actors:
                now.context = name
                command = signaler.process(price, now)
                trader.process(command, price, now)
        [h.update(price) for h in histories]
    return plot
      
class Actor(object):
    '''wrap an actor (signaler, trader, histories) component'''
    
    def __init__(self, name):
        self.name = name
        self.trader = None
        self._signaler = None
        self.histories = []
    
    @property
    def signaler(self):
        return self._signaler
    
    @signaler.setter
    def signaler(self, signaler_histories):
        self._signaler, self.histories = signaler_histories
        
    def __str__(self):
        return "("+ self.name + "|" + repr(self.trader) + "|" + repr(self._signaler) + "|" + repr(self.histories) + ")" 
      
class PipeSpec(object):
    '''Holds all required components of a processing pipe.'''
    
    def __init__(self):
        self.actors = []
        
    def add(self, actor):
        self.actors.append(actor)
        
    def merge(self, other):
        '''Create a new PipeSpec that is the union of this and given.'''
        union = PipeSpec()
        union.actors = self.actors + other.actors
        return union
    
    def invoke(self, index):
        histories = [history for actor in self.actors for history in actor.histories]
        actors = [(actor.name, actor.signaler, actor.trader) for actor in self.actors]
        return process(index, actors, histories)      

        
class MultiValuePoint(object):
    '''Associates a single x value with multiple context->key->value triples'''
    
    def __init__(self, x, price):
        self.x = x
        self.price = price
        self._y = { }
        self._context = None
        
    def get(self, context, key):
        return self._y[context][key]
    
    def set(self, context, key, value):
        self._y[context][key] = value
        
    def get_all_contexts(self):
        return self._y
    
    @property
    def context_names(self):
        return self._y.keys()
        
    @property        
    def y(self):
        return self._y[self.context]
    
    @property
    def context(self):
        return self._context
    
    @context.setter
    def context(self, new_context):
        if not self._y.has_key(new_context):
            self._y[new_context] = {}
        self._context = new_context      
      
      
class History():
    '''Maintains a variable length history of prices'''
  
    def __init__(self, size):
        '''Initialize a History of size length with all zeros.'''
        self._missing = size
        self._values = deque([0] * size, size)
    
    def update(self, price):
        '''Advance history by dropping the first price and appending the given price.'''
        self._values.append(price)
        if self._missing > 0:
            self._missing -= 1
        return self
    
    def is_ready(self):
        '''Check whether history has been advanced at least size times.'''
        return self._missing <= 0
    
    def mean(self):
        return sum(self._values) / len(self._values)
    
    def lowest(self):
        return min(self._values)
    
    def highest(self):
        return max(self._values)
